module AboutYou
  module SDK
    ###
    # the QueryBuilder is a module supporting the query with mroe complex logic
    # concerning the building of the queries for the api-call
    #
    # Author:: Collins GmbH & Co KG
    ###
    module QueryBuilder
      ###
      # builds the autocompletion-query
      #
      # * *Args*    :
      #   - +searchword+ -> a String containing the word to search completitions for
      #   - +limit+ -> Maximum number of results [optional]
      #   - +types+ -> Array of types to search for [optional]
      #
      # * *Fails* :
      #   - if the searchword is not a String
      #   - if the limit cant be transformed into an integer
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_autocomplete(searchword, limit = nil, types = nil)
        unless searchword.is_a? String
          fail 'InvalidArgumentException! searchword must be a string'
        end

        # downcase is a workaround of ticket SAPI-532
        options = { 'searchword' => searchword.downcase }

        unless limit.nil?
          fail 'InvalidArgumentException! limit must be an integer' unless
          limit.is_a?(Integer) || limit[/\d/]
          options['limit'] = Integer(limit)
        end
        
        options['types'] = types unless types.empty?

        query.push(
          'autocompletion' => options
        )

        self
      end

      ###
      # builds the basket-query
      #
      # * *Args*    :
      #   - +session_id+ -> a String containing the session id of an user
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_basket(session_id)
        check_session_id(session_id)

        query.push(
          'basket' => {
            'session_id' => session_id
          }
        )

        self
      end

      ###
      # builds the basket-query
      #
      # * *Args*    :
      #   - +session_id+ -> a String containing the session id of an user
      #   - +items+ -> an Array containing all of the items which should be added
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def add_items_to_basket(session_id, items)
        check_session_id(session_id)

        order_lines = []

        items.each do |item|
          order_line = {
            'id' => item.id,
            'variant_id' => item.variant_id
          }

          if item.additional_data
            order_line['additional_data'] = item.additional_data
          end

          order_lines.push(order_line)
        end

        query.push(
          'basket' => {
            'session_id' => session_id,
            'order_lines' => order_lines
          }
        )

        self
      end

      ###
      # builds the basket-query
      #
      # * *Args*    :
      #   - +session_id+ -> a String containing the session id of an user
      #   - +itemSets+ -> an instance of AboutYou::SDK::Model::BasketSet
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def add_itemsets_to_basket(session_id, itemsets)
        check_session_id(session_id)

        order_lines = []

        itemsets.each do |item_set|
          order_line = {
            'id' => item_set.id,
            'set_items' => []
          }

          if item_set.additional_data
            order_line['additional_data'] = item_set.additional_data
          end

          item_set.items.each do |item|
            entry = {
              'variant_id' => item.variant_id
            }
            if item.additional_data
              entry['additional_data'] = item.additional_data
            end
            order_line['set_items'].push(entry)
          end

          order_lines.push(order_line)
        end

        query.push(
          'basket' => {
            'session_id' => session_id,
            'order_lines' => order_lines
          }
        )

        self
      end

      ###
      # builds the basket-query
      #
      # * *Args*    :
      #   - +session_id+ -> a String containing the session id of an user
      #   - +productVariantId+ -> ID of product variant
      #   - +basketItemId+ -> ID of single item or set in the basket
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def add_to_basket(session_id, product_variant_id, basket_item_id)
        check_session_id(session_id)

        query.push(
          'basket' => {
            'session_id' => session_id,
            'order_lines' => [{
              'id' => basket_item_id,
              'variant_id' => Integer(product_variant_id)
            }]
          }
        )

        self
      end

      ###
      # builds the basket-query
      #
      # * *Args*    :
      #   - +session_id+ -> a String containing the session id of an user
      #   - +itemIds+ -> array of basket item ids to delete, this can be sets or single items
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def remove_from_basket(session_id, item_ids)
        check_session_id(session_id)

        order_lines = []

        item_ids.each do |id|
          order_lines.push(
            'delete' => id
          )
        end

        query.push(
          'basket' => {
            'session_id' => session_id,
            'order_lines' => order_lines
          }
        )

        self
      end

      ###
      # builds the basket-query
      #
      # * *Args*    :
      #   - +session_id+ -> a String containing the session id of an user
      #   - +basket+ -> an instance of AboutYou::SDK::Model::Basket
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def update_basket(session_id, basket)
        check_session_id(session_id)

        order_lines = basket.order_lines_array
        basket_query = { 'session_id'  => session_id }
        basket_query['order_lines'] = order_lines unless order_lines.empty?

        query.push(
          'basket' => basket_query
        )

        self
      end

      ###
      # builds the category-query
      #
      # * *Args*    :
      #   - +ids+ -> either a single category ID as integer or an array of IDs
      #
      # * *Fails* :
      #   - if a category id is smaller then 1
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_categories_by_ids(ids = nil)
        if ids.nil?
          query.push(
            'category' => nil
          )
        else
          # we allow to pass a single ID instead of an array
          ids = Array(ids)

          ids.each do |id|
            id = Integer(id)
            if id < 1
              fail 'InvalidArgumentException! A single category ID must be greater than 0'
            end
          end

          ids = ids.map { |id| Integer(id) }

          query.push(
            'category' => {
              'ids' => ids
            }
          )
        end

        self
      end

      ###
      # builds the category_tree-query
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_category_tree
        query.push(
          'category_tree' => {
            'version' => '2'
          }
        )

        self
      end

      ###
      # builds the products-query
      #
      # * *Args*    :
      #   - +ids+ -> Either a single id or an Array of ids which should be fetched
      #   - +fields+ -> Additional product fields which should be fetched for each product [optional]
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_products_by_ids(ids, fields = [])
        # we allow to pass a single ID instead of an array
        ids = Array(ids).map { |id| Integer(id) }

        query.push(
          'products' => {
            'ids' => ids,
            'fields' => AboutYou::SDK::Criteria::ProductFields.filter_fields(fields)
          }
        )
        self
      end

      ###
      # builds the live_variant-query
      #
      # * *Args*    :
      #   - +ids+ -> Either a single id or an Array of ids which should be fetched
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_live_variant_by_ids(ids)
        # we allow to pass a single ID instead of an array
        ids = Array(ids)
        ids = ids.map { |id| Integer(id) }

        query.push(
          'live_variant' => {
            'ids' => ids
          }
        )

        self
      end

      ###
      # builds the products_eans-query
      #
      # * *Args*    :
      #   - +eans+ -> Either a single ean or an Array of eans which should be fetched
      #   - +fields+ -> Additional product fields which should be fetched for each product [optional]
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_products_by_eans(eans, fields = [])
        query.push(
          'products_eans' => {
            'eans'   => eans,
            'fields' => AboutYou::SDK::Criteria::ProductFields.filter_fields(fields),
            'version' => '2'
          }
        )

        self
      end

      ###
      # builds the get_order-query
      #
      # * *Args*    :
      #   - +orderId+ -> id of the order which should be fetched
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_order(order_id)
        query.push(
          'get_order' => {
            'order_id' => order_id
          }
        )

        self
      end

      ###
      # builds the initiate_order-query
      #
      # * *Args*    :
      #   - +session_id+ -> A String containing the sessionId of the User
      #   - +successUrl+ -> callback URL if the order was OK
      #   - +cancelUrl+ -> callback URL if the order was canceled [optional]
      #   - +errorUrl+ -> callback URL if the order had any exceptions [optional]
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def initiate_order(session_id, success_url, cancel_url, error_url)
        check_session_id(session_id)

        args = {
          'session_id' => session_id,
          'success_url' => success_url
        }
        args['cancel_url'] = cancel_url if cancel_url
        args['error_url'] = error_url if error_url

        query.push(
          'initiate_order' => args
        )

        self
      end

      ###
      # builds the product_search-query
      #
      # * *Args*    :
      #   - +criteria+ -> Hash containing one or multiple search terms
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_product_search(criteria)
        check_session_id(criteria.session_id)

        query.push(
          'product_search' => criteria.to_array
        )

        self
      end

      ###
      # builds the facets-query
      #
      # * *Args*    :
      #   - +group_ids+ -> Array of group ids [optional]
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_facets(group_ids = [])
        group_ids = group_ids.map { |groupId| Integer(groupId) } if group_ids

        query.push(
          'facets' => {
            'group_ids' => group_ids
          }
        )

        self
      end

      ###
      # builds the facet-query
      #
      # * *Args*    :
      #   - +params+ -> Hash containing 2 keys: "id", "group_id"
      #
      # * *Fails* :
      #   - if params is not set
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_facet(params)
        fail 'InvalidArgumentException! no params given' if params.empty?

        query.push(
          'facet' => params
        )

        self
      end

      ###
      # builds the facet_types-query
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_facet_types
        query.push(
          'facet_types' => nil
        )

        self
      end

      ###
      # builds the suggest-query
      #
      # * *Args*    :
      #   - +searchword+ -> the searchs tring to search for
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_suggest(searchword)
        query.push(
          'suggest' => {
            'searchword' => searchword
          }
        )

        self
      end
      
      ###
      # builds the spell_correction
      #
      # * *Args*    :
      #   - +searchword+ -> the search string to search for
      #   - +category_ids+ -> the cat ids to filter for
      #
      # * *Fails* :
      #   - if the searchword is not a String
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_spell_correction(searchword, category_ids = nil)
        fail 'InvalidArgumentException! searchword must be a string' unless
        searchword.is_a?(String)

        options = {
            'searchword' => searchword
        }
        options['filter'] = {
            'categories' => category_ids
        } unless category_ids.empty?

        query.push(
            'did_you_mean' => options
        )

        self
      end

      ###
      # builds the child_apps-query
      #
      # * *Returns* :
      #   - Instance of AboutYou::SDK::Query
      ###
      def fetch_child_apps
        query.push(
          'child_apps' => nil
        )

        self
      end

      ###
      # turns all queries built in here into a json string
      #
      # * *Returns* :
      #   - Json string containing all queries built in here
      ###
      def query_string
        query.to_json
      end

      ###
      # checks whether the session id is valid or not
      #
      # * *Args*    :
      #   - +session_id+ -> the session id of a user
      #
      # * *Fails* :
      #   - if the session id is not a String
      #   - if the session id has less then 4 characters
      ###
      def check_session_id(session_id)
        fail 'InvalidArgumentException! The session id must be a string' unless
        session_id.is_a? String
        fail 'InvalidArgumentException! The session id must have at least
          5 characters' unless session_id.length > 4
      end
    end
  end
end
